﻿using System;
using System.Collections.Generic;

namespace swe.Core
{
	public class StateMachine<T, TR> : IStateMachineBuilder<T, TR>, IStateMachineController<T, TR>, IStableStateMachine<TR>
	{
		#region Fields

		private string _nextState;
		private string _currentState;
		private T _additionalObject;
		private bool _eos;
		private readonly Dictionary<string, State<T, TR>> _states = new Dictionary<string, State<T, TR>>();
		private TR _returnData;
		
		#endregion

		#region Ctors And Static Factories

		private StateMachine()
		{
		}

		public static IStateMachineBuilder<T, TR> Create(string initialState)
		{
			var rv = new StateMachine<T, TR>();
			rv.JumpToState(initialState);
			return rv;
		}

		#endregion

		#region IStateMchine Implementation

		public bool Eos { get { return _eos; } }
		public string CurrentState { get { return _currentState; } }
		public bool HasReturn
		{
			get
			{
				return !EqualityComparer<TR>.Default.Equals(_returnData, default(TR));
			}
		}


		public StateType CurrentStateType { get { return _states[_currentState].StateType; } }

		public bool Process()
		{
			if (_eos)
				throw new InvalidOperationException("StateMachine is already in the finished state.");
			if (_currentState == null)
			{
				_currentState = _nextState;
			}
			if (_nextState == null)
				throw new InvalidOperationException("State machine has no initial state.");
			if (!_states.ContainsKey(_nextState))
				throw new InvalidOperationException(string.Format("State {0} does not exist.", _nextState));
			_returnData = default(TR);
			var toRun = _states[_nextState].Func;
			_currentState = _nextState;
			var rv = toRun(this, _additionalObject);
			if (rv.Result.Equals(StateResult.BreakFlow))
				_eos = true;
			else if (rv.Result.Equals(StateResult.LoopCurrentState))
				_nextState = _currentState;
			else if (rv.Result.Equals(StateResult.JumpToState))
				_nextState = rv.ResultState;
			return _eos;
		}

		public TR GetRturnData()
		{
			if (!HasReturn)
				throw new InvalidOperationException("There is no return data at this state.");
			return _returnData;
		}

		#endregion

		#region IStateMchineBuilder Implementation

		public IStateMachineBuilder<T, TR> State(string stateName, Func<IStateMachineController<T, TR>, T, StateResult> func, StateType stateType = StateType.Statble)
		{
			AddState(stateName, func, stateType);
			return this;
		}

		public IStateMachine<TR> BindTo(T initialObj)
		{
			_additionalObject = initialObj;
			return this;
		}

		#endregion

		#region ISateMachineController Implementation

		public void UpdateObject(T additionalObj)
		{
			_additionalObject = additionalObj;
		}

		public void Return(TR data)
		{
			_returnData = data;
		}
		
		#endregion

		#region IStableStateMchine implementation

		public bool ProcessToStableState()
		{
			while (true)
			{
				var rv = Process();
				if (CurrentStateType == StateType.Statble || Eos)
					return rv;
			}
		}
		
		#endregion

		#region Private Methods

		private void JumpToState(string state)
		{
			_nextState = state;
		}

		private void AddState(string stateName, Func<StateMachine<T, TR>, T, StateResult> func, StateType stateType)
		{
			_states.Add(stateName, new State<T, TR> { Name = stateName, Func = func, StateType = stateType });
		}
 
		#endregion	
	}
}
